有向图的最短路径问题(二)——Dijkstra算法

有向图的最短路径问题(一)中我们介绍了可以求任意两点间最短距离的Floyd算法,虽然Floyd算法实现起来简单,但是其效率较低(O(n**3))。在本篇我们将介绍求某一点到图中所有点最短距离的算法——Dijkstra算法。

Dijkstra算法

image.png

使用Dijkstra算法求某一点到图中所有点最短距离有两种方法,分别使用的数据结构为邻接矩阵(python中以一个多重列表或矩阵表示)和邻接表(python中使用字典来表示)。但是无论使用那种数据结构,他的算法原理都是相同的,以上图为例,具体可分为下面四步:
(1)找出距离当前节点(即起点S)最近的节点,也即权重最小的边,前往该节点(即B);
(2)对于该节点(B)的邻居,较之已有的路径,检查是否有前往他们更短路径,如果有,就更新其开销(譬如SA=6,但是经过B点再到A的开销为5,小于6,更新);
(3)更新完所有邻居节点的开销后,又回到第(1)步,即选择距离当前节点最近的节点(已前往过的节点跳过),前往该节点并更新邻居的开销;循环这个过程,直到每个节点都走过;
(4)计算最终路径。

1、使用邻接矩阵

这里我们借用http://wiki.jikexueyuan.com/project/easy-learn-algorithm/dijkstra.html的例子,其有向加权图如下:

image.png

他的邻接矩阵e表示如下:
image.png

此外还需要初始化一个起始点到所有点的距离开销的数组(为方便我们命名为dis),后续我们更新的开销即为此数组中的开销:
初始距离

1)查找距离起点最近的点,即为dis中的最小的点,此处为dis[2]=1,即我们确定离起点最近的点一定为2;
2)接着我们更新点2相邻点<1,3,4>的开销,2_3=9, 2_4=3,然后我们比较经过2到达3,4的开销与1直接到达3,4(即dis[3],dis[4])的开销,我们发现dis[3]=12大于dis[2]+2_3=10,于是更新1到3的开销;同样的dis[4]=∞大于dis[2]+2_4=4,于是也更新dis[4]的开销为4(由于1为起点,所以这里不用考虑dis[1]与1_2+2_1的关系,虽然计算也是小于后者);更新完的dis数组如下所示:
更新点2相邻点

3)继续寻找剩余的点(3,4,5,6)中距离起点最近的点(此处为dis[4]=4,即确定了点4离点1的最近距离为4)来更新相邻点<2,3,5,6>的开销。dis[2]为已经确定了的,不用计算,虽然计算了也是不用更新。dis[3]=10大于dis[4]+4_3=8,于是更新其开销为8,同理更新dis[5]=17,dis[6]=19,结果如下:
更新点4相邻点

4)然后继续寻找剩下点(3,5,6)中的最小值,此处为3,也即确定了起点到点3的最近距离为dis[3]=8,然后更新其相邻点,结果为:
更新点3相邻点

5)继续寻找剩余的点(5,6)中的最小值,此处为5,即确定了起点到点5的最近距离为dis[5],更新相邻点结果为:
更新点5

6)最后确定6号点的最小值,更新其相邻点的开销(其实也没改变)。完成,1号点到所有点的最小值。
下面是上述例子的Dijkstra算法python实现代码:

# edges表示邻接矩阵
edges = [[0, 1, 12, 666, 666, 666],
         [666, 0, 9, 3, 666, 666],
         [666, 666, 0, 666, 5, 666],
         [666, 666, 4, 0, 13, 15],
         [666, 666, 666, 666, 0, 4],
         [666, 666, 666, 666, 666, 0]]
dis = {2: 1, 3: 12, 4: 666, 5: 666, 6: 666}
visited = []

min_dis = None
min_dis_point = None

for i in range(len(dis)):
    sort_dis = sorted(dis.items(), key=lambda item: item[1])
    # 找到dis中距离起始点距离最小的点
    for p, d in sort_dis:
        if p not in visited:
            min_dis_point = p
            min_dis = d
            visited.append(p)
            break
    for j in range(len(edges)):
        # 权重小于666的为相邻点
        if edges[min_dis_point-1][j] < 666:
            update = min_dis + edges[min_dis_point-1][j]
            # 若经过min_dis_point到j的距离比起点直达j的距离大,则更新
            if dis[j+1] > update:
                dis[j+1] = update

print(dis)

# 由于python中列表的索引是从0开始的,而我们的点是1-6,因此min_dis_point在邻接矩阵中的位置需要减一,
# 这里容易犯错,若是将点改为从0开始无论是写还是可读性都会好很多

2、使用邻接表

无论使用哪种数据结构,算法原理都是一样的,因此这里仅讨论使用邻接表时如何用Python代码实现。
由于python中没有指针,因此这里需要用嵌套字典来表示邻接表,形式为{节点:{相邻节点:连线权重}}

adj = {1: {2: 1, 3: 12}, 2: {4: 3, 3: 9}, 3: {5: 5}, 4: {3: 4, 5: 13, 6: 15}, 5: {6: 4}}
dis = {2: 1, 3: 12, 4: 666, 5: 666, 6: 666}
parents_node = {2: 1}
visited = []

min_dis = None
min_dis_point = None
for i in range(len(dis)):
    sort_dis = sorted(dis.items(), key=lambda item: item[1])
    # 找到dis中距离起始点距离最小的点
    for p, d in sort_dis:
        if p not in visited:
            min_dis_point = p
            min_dis = d
            visited.append(p)
            break
    try:
        # 更新相邻点的开销
        for n in adj[min_dis_point].keys():
            update = min_dis+adj[min_dis_point][n]
            if dis[n] > update:
                dis[n] = update
                parents_node[n] = min_dis_point
    except:  # 最后一个点6有进没出,因此没有相邻点会报错
        pass

travel = ''
for node, parents in parents_node.items():
    travel += str(parents)+'->'+str(node)+'->'

print(dis)
print(travel)

从某种程度来说使用邻接表可能更容易理解些,由于不用使用索引,书写代码时也没那么容易出错,同时由于不用储存那些不相邻的节点权重,也即邻接矩阵中的的那些无穷大的值,因此也可以节约内存。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容

  • 最短路径算法在现实生活中也具有非常多的应用,例如在一个复杂的景区,想要从一个景点到另外一个景点,利用最短路径算法就...
    郑明明阅读 1,796评论 0 6
  • 定义 所谓最短路径问题是指:如果从图中某一顶点(源点)到达另一顶点(终点)的路径可能不止一条,如何找到一条路径使得...
    JarryWell阅读 87,522评论 2 27
  • 参见贪心算法——最短路径Dijkstra算法参见动态规划 目录 0.最短路径问题0.1 最短路径问题描述 0.1....
    王侦阅读 4,634评论 1 9
  • 回想起曾学习A-star寻径算法时,难以透彻理解其原理和机制,但随着对图和搜索算法的理解愈发深入,近期重拾A-st...
    胡哈哈哈阅读 4,138评论 0 1
  • 提到演义里的公孙康,印象深刻。一来戏份实在就那么点儿,二来名字容易联想到金庸小说里的反派。遥想那一年,郭嘉早逝,留...
    哑巢父阅读 276评论 0 0